import pandas as pd
from collections import defaultdict


# ======================
# 一、数据准备与预处理
# ======================
def load_and_clean_data(file_path):
    try:
        df = pd.read_excel(file_path)
    except Exception as e:
        raise FileNotFoundError(f"文件读取失败: {str(e)}")

    required_cols = ['issue', 'hundred', 'ten', 'one']
    if not all(col in df.columns for col in required_cols):
        raise ValueError("数据列名缺失，请包含：issue, hundred, ten, one")

    df = df[required_cols].copy()
    # 过滤非法数值（0-9）
    df = df[(df['hundred'].between(0, 9)) &
            (df['ten'].between(0, 9)) &
            (df['one'].between(0, 9))]
    # 转换为整数类型
    df = df.astype({'hundred': int, 'ten': int, 'one': int})
    # 按期号排序并重置索引
    df = df.sort_values('issue').reset_index(drop=True)
    return df


# ======================
# 二、辅助函数定义
# ======================
def get_cold_numbers(series, i, window=30):
    """获取冷号（最近window期内出现次数最少的号码）"""
    if i < window:
        recent = series.iloc[:i]  # 数据不足时取全部
    else:
        recent = series.iloc[i - window:i]
    freq = recent.value_counts()
    if freq.empty:
        return []
    min_count = freq.min()
    cold_numbers = freq[freq == min_count].index.tolist()
    return cold_numbers[:2]  # 默认杀2个冷号


def get_span_kill(row, threshold=3):
    """跨度范围杀十位号码（示例逻辑：跨度小于阈值时杀当前十位）"""
    span = row.max() - row.min()
    return [row['ten']] if span < threshold else []


def get_hot_numbers(series, i, window=10, top_n=2):
    """获取热号（最近window期内出现次数最多的号码）"""
    if i < window:
        recent = series.iloc[:i]
    else:
        recent = series.iloc[i - window:i]
    freq = recent.value_counts()
    if freq.empty:
        return []
    max_count = freq.max()
    hot_numbers = freq[freq == max_count].index.tolist()
    return hot_numbers[:top_n]  # 杀前top_n个热号


def get_subtract_kill(df, i, pos='hundred'):
    """两码绝杀法（当前位与前两位的差值）"""
    if i < 2:
        return []
    prev1 = df.iloc[i - 1][pos]
    prev2 = df.iloc[i - 2][pos]
    diff = abs(prev1 - prev2)
    return [diff] if diff != df.iloc[i][pos] else []


def get_reverse_span_kill(df, i, pos='hundred'):
    """跨度反向操作法（最近3期跨度趋势）"""
    if i < 2:
        return []
    current_span = df[['hundred', 'ten', 'one']].iloc[i].max() - df[['hundred', 'ten', 'one']].iloc[i].min()
    trend = []
    for j in range(max(0, i - 2), i):  # 取前2期跨度
        prev_span = df[['hundred', 'ten', 'one']].iloc[j].max() - df[['hundred', 'ten', 'one']].iloc[j].min()
        trend.append(1 if current_span > prev_span else -1)
    return [10 - current_span] if sum(trend) >= 1 else [current_span]


def get_road_kill(df, i, pos):
    """路数冷热杀（0:0369路，1:147路，2:258路）"""
    nums = df[pos].iloc[max(0, i - 15):i + 1]  # 最近15期数据
    road_counts = {0: 0, 1: 0, 2: 0}
    for num in nums:
        road = num % 3
        road_counts[road] += 1
    coldest_road = min(road_counts, key=lambda k: road_counts[k])
    # 杀冷门路数的所有号码
    return [x for x in range(10) if x % 3 == coldest_road]


def get_odd_even_kill(series, i, window=20):
    """奇偶比例杀十位号码（奇偶失衡时杀对应类型）"""
    if i < window:
        recent = series.iloc[:i]
    else:
        recent = series.iloc[i - window:i]
    odd = recent[recent % 2 == 1].count()
    even = len(recent) - odd
    return [0, 2, 4, 6, 8] if odd > even else [1, 3, 5, 7, 9]


def get_group_kill(series, i, group_size=3):
    """余数分组杀十位号码（杀同组其他号码）"""
    current = series.iloc[i]
    group = [x for x in range(10) if x % group_size == current % group_size]
    return group[1:]  # 杀除当前外的同组号码


def get_sum_tail_kill(df, i, pos='ten', offset=4):
    """和值尾+4杀十位（当前和值尾+4取模10）"""
    current_row = df[['hundred', 'ten', 'one']].iloc[i]
    sum_tail = current_row.sum() % 10
    kill_num = (sum_tail + offset) % 10
    return [kill_num]


def get_sum_kill(row):
    """和值范围杀个位号码（和值<5或>22时杀当前个位）"""
    s = row.sum()
    return [row['one']] if s < 5 or s > 22 else []


def get_prime_kill(series, i, window=15):
    """质合数杀个位号码（质数多则杀质数，否则杀合数）"""
    primes = [2, 3, 5, 7]
    if i < window:
        recent = series.iloc[:i]
    else:
        recent = series.iloc[i - window:i]
    prime_count = recent[recent.isin(primes)].count()
    composite_count = len(recent) - prime_count
    return primes if prime_count > composite_count else [x for x in range(10) if x not in primes]


def get_shape_kill(series, i):
    """形态特征杀个位号码（杀重复号或连号）"""
    if i == 0:
        return []
    current = series.iloc[i]
    prev = series.iloc[i - 1]
    # 杀重复号
    if current == prev:
        return [current]
    # 杀连号（差值为1）
    if abs(current - prev) == 1:
        return [current]
    return []


def get_sum_kill(row):
    """和值范围杀个位号码（和值<5或>22时杀当前个位）"""
    s = row.sum()
    return [row['one']] if s < 5 or s > 22 else []


def calculate_cost(predictions):
    """计算直选复式投注成本（每注2元）"""

    def remain_count(kill_list):
        return max(0, 10 - len(kill_list))  # 杀号不超10个

    h_remain = remain_count(predictions.get('hundred', []))
    t_remain = remain_count(predictions.get('ten', []))
    o_remain = remain_count(predictions.get('one', []))
    return h_remain * t_remain * o_remain * 2


# ======================
# 三、模型定义（修正语法格式）
# ======================
MODELS = {
    # 百位模型
    'hundred_cold': {
        'name': '冷号杀',
        'window': 30,
        'func': lambda df, i: get_cold_numbers(df['hundred'], i, window=30),
        'pos': 'hundred',
        'kill_count': 2
    },
    'hundred_span': {
        'name': '跨度范围杀',
        'window': 0,
        'func': lambda df, i: get_span_kill(df[['hundred', 'ten', 'one']].iloc[i], threshold=3),
        'pos': 'hundred',
        'kill_count': 1
    },
    'hundred_sub': {
        'name': '两码绝杀法',
        'window': 2,
        'func': lambda df, i: get_subtract_kill(df, i),
        'pos': 'hundred',
        'kill_count': 2
    },
    'hundred_reverse': {
        'name': '跨度反向操作法',
        'window': 3,
        'func': lambda df, i: get_reverse_span_kill(df, i),
        'pos': 'hundred',
        'kill_count': 1
    },
    'hundred_formula': {
        'name': '百位杀码公式',
        'window': 0,
        'func': lambda df, i: [(df['hundred'].iloc[i] * 3 + 3) % 10],
        'pos': 'hundred',
        'kill_count': 1
    },

    # 十位模型
    'ten_hot': {
        'name': '热号杀',
        'window': 10,
        'func': lambda df, i: get_hot_numbers(df['ten'], i, window=10, top_n=2),
        'pos': 'ten',
        'kill_count': 2
    },
    'ten_odd_even': {
        'name': '奇偶比例杀',
        'window': 20,
        'func': lambda df, i: get_odd_even_kill(df['ten'], i, window=20),
        'pos': 'ten',
        'kill_count': 1
    },
    'ten_group': {
        'name': '余数分组杀',
        'window': 0,
        'func': lambda df, i: get_group_kill(df['ten'], i, group_size=3),
        'pos': 'ten',
        'kill_count': 2
    },
    'ten_sum': {
        'name': '和值尾+4杀十位',
        'window': 0,
        'func': lambda df, i: get_sum_tail_kill(df, i, pos='ten', offset=4),
        'pos': 'ten',
        'kill_count': 1
    },
    'ten_road': {
        'name': '路数冷热杀',
        'window': 15,
        'func': lambda df, i: get_road_kill(df, i, pos='ten'),
        'pos': 'ten',
        'kill_count': 1
    },

    # 个位模型
    'one_sum': {
        'name': '和值范围杀',
        'window': 0,
        'func': lambda df, i: get_sum_kill(df[['hundred', 'ten', 'one']].iloc[i]),
        'pos': 'one',
        'kill_count': 2
    },
    'one_prime': {
        'name': '质合数杀',
        'window': 15,
        'func': lambda df, i: get_prime_kill(df['one'], i, window=15),
        'pos': 'one',
        'kill_count': 1
    },
    'one_shape': {
        'name': '形态特征杀',
        'window': 0,
        'func': lambda df, i: get_shape_kill(df['one'], i),
        'pos': 'one',
        'kill_count': 2
    },
    'one_sub': {
        'name': '个位两码绝杀',
        'window': 2,
        'func': lambda df, i: get_subtract_kill(df, i, pos='one'),
        'pos': 'one',
        'kill_count': 1
    },
    'one_reverse': {
        'name': '个位跨度反向',
        'window': 3,
        'func': lambda df, i: get_reverse_span_kill(df, i, pos='one'),
        'pos': 'one',
        'kill_count': 1
    }
}


# ======================
# 四、模型验证与预测
# ======================
def validate_models(df, models):
    results = defaultdict(lambda: {'correct': 0, 'total': 0, 'kill_num': 0})

    for model_key, model in models.items():
        pos = model['pos']
        window = model.get('window', 0)
        kill_num = model.get('kill_count', 1)

        # 验证从window期开始的期数（确保有足够历史数据）
        valid_indices = df.index[window:].tolist()
        for i in valid_indices:
            actual = df.at[i, pos]
            try:
                kill_list = model['func'](df, i)
            except Exception as e:
                print(f"模型{model_key}在期号{df.at[i, 'issue']}计算异常: {e}")
                kill_list = []

            # 限制杀号数量不超过模型定义
            kill_list = kill_list[:kill_num]
            results[model_key]['kill_num'] += len(kill_list)

            # 统计正确数（实际未出现在杀号列表中）
            if actual not in kill_list:
                results[model_key]['correct'] += 1
            results[model_key]['total'] += 1

    # 计算正确率
    for model_key in results:
        total = results[model_key]['total']
        correct = results[model_key]['correct']
        rate = (correct / total * 100) if total > 0 else 0.0
        results[model_key]['rate'] = round(rate, 2)

    return results


def predict_kill_numbers(filtered_models, df):
    latest_issue_idx = len(df) - 1
    predictions = defaultdict(list)

    # 按位置分组模型
    pos_models = defaultdict(list)
    for model_key in filtered_models:
        pos = MODELS[model_key]['pos']
        pos_models[pos].append(model_key)

    # 各位置最终杀号数量配置（可根据需求调整）
    pos_kill_limit = {'hundred': 4, 'ten': 4, 'one': 4}

    for pos in ['hundred', 'ten', 'one']:
        if pos not in pos_models:
            continue

        combined_kills = set()
        for model_key in pos_models[pos]:
            model = MODELS[model_key]
            # 获取当前最新期的杀号（使用全部历史数据）
            kill_list = model['func'](df, latest_issue_idx)
            # 合并并去重，按模型杀号数量截取
            combined_kills.update(kill_list[:model['kill_count']])

        # 应用最终杀号数量限制
        final_kills = list(combined_kills)[:pos_kill_limit[pos]]
        predictions[pos] = sorted(final_kills)

    return predictions


# ======================
# 五、主程序
# ======================
if __name__ == "__main__":
    file_path = './3d.xlsx'  # 替换为实际文件路径

    # 数据加载与清洗
    try:
        df = load_and_clean_data(file_path)
        print(f"数据加载成功，共{len(df)}条有效记录")
    except Exception as e:
        print(f"数据加载失败: {e}")
        exit()

    # 模型验证（仅验证窗口期内的数据）
    model_results = validate_models(df, MODELS)

    # 筛选正确率>70%的高准确率模型
    high_accuracy_models = {k: v for k, v in model_results.items() if v['rate'] > 70}

    # 生成杀号预测（使用高准确率模型）
    kill_predictions = predict_kill_numbers(high_accuracy_models, df)

    # 输出结果
    print("\n=== 高准确率模型清单 ===")
    for model_key, stats in high_accuracy_models.items():
        print(f"{model_key} ({MODELS[model_key]['name']}): "
              f"正确率{stats['rate']}% | 杀胆数{stats['kill_num']} | "  # 从stats获取kill_num
              f"命中{stats['total'] - stats['correct']}次（验证{stats['total']}期）")

    print("\n=== 最新杀胆预测 ===")
    for pos, kills in kill_predictions.items():
        pos_name = None
        if pos == 'hundred':
            pos_name = '百'
        elif pos == 'ten':
            pos_name = '十'
        elif pos == 'one':
            pos_name = '个'
        else:
            pos_name = '未知'
        print(f"{pos_name}位杀胆: {kills if kills else '无'}")

    # 计算投注成本
    cost = calculate_cost(kill_predictions)
    print(f"\n直选复式投注成本: {cost}元（每注2元）")